{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\nPyTorch: Custom nn Modules\n--------------------------\n\nA third order polynomial, trained to predict $y=\\sin(x)$ from $-\\pi$\nto $pi$ by minimizing squared Euclidean distance.\n\nThis implementation defines the model as a custom Module subclass. Whenever you\nwant a model more complex than a simple sequence of existing Modules you will\nneed to define your model this way.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import torch\nimport math\n\n\nclass Polynomial3(torch.nn.Module):\n    def __init__(self):\n        \"\"\"\n        In the constructor we instantiate four parameters and assign them as\n        member parameters.\n        \"\"\"\n        super().__init__()\n        self.a = torch.nn.Parameter(torch.randn(()))\n        self.b = torch.nn.Parameter(torch.randn(()))\n        self.c = torch.nn.Parameter(torch.randn(()))\n        self.d = torch.nn.Parameter(torch.randn(()))\n\n    def forward(self, x):\n        \"\"\"\n        In the forward function we accept a Tensor of input data and we must return\n        a Tensor of output data. We can use Modules defined in the constructor as\n        well as arbitrary operators on Tensors.\n        \"\"\"\n        return self.a + self.b * x + self.c * x ** 2 + self.d * x ** 3\n\n    def string(self):\n        \"\"\"\n        Just like any class in Python, you can also define custom method on PyTorch modules\n        \"\"\"\n        return f'y = {self.a.item()} + {self.b.item()} x + {self.c.item()} x^2 + {self.d.item()} x^3'\n\n\n# Create Tensors to hold input and outputs.\nx = torch.linspace(-math.pi, math.pi, 2000)\ny = torch.sin(x)\n\n# Construct our model by instantiating the class defined above\nmodel = Polynomial3()\n\n# Construct our loss function and an Optimizer. The call to model.parameters()\n# in the SGD constructor will contain the learnable parameters (defined \n# with torch.nn.Parameter) which are members of the model.\ncriterion = torch.nn.MSELoss(reduction='sum')\noptimizer = torch.optim.SGD(model.parameters(), lr=1e-6)\nfor t in range(2000):\n    # Forward pass: Compute predicted y by passing x to the model\n    y_pred = model(x)\n\n    # Compute and print loss\n    loss = criterion(y_pred, y)\n    if t % 100 == 99:\n        print(t, loss.item())\n\n    # Zero gradients, perform a backward pass, and update the weights.\n    optimizer.zero_grad()\n    loss.backward()\n    optimizer.step()\n\nprint(f'Result: {model.string()}')"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.6.12"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}